
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

#include "common/alloc.h"
#include "exhash/exhash_tables.h"

// global tab space constant, equals to 4 spaces
static const char * g_ts = "    ";

static void print_arr_elem(struct ExHashTable * e, size_t index)
{
    ExHashInt value = e->arr[index];

    if (value == EX_HASH_INT_MAX)
    {
        fprintf(stdout, "0x%08X,", value);
    }
    else
    {
        fprintf(stdout, "0x%08X /* '%c' */,", value, (char)index);
    }
}

static void rec_print(struct ExHashTable * e, const char * var_prefix, int level)
{
    if (e->next != NULL)
    {
        rec_print(e->next, var_prefix, level + 1);
    }

    fprintf(stdout, "static struct ExHashTable %s%d =\n{\n", var_prefix, level);

    // print `arr'
    fprintf(stdout, "%s{", g_ts);
    size_t i;
    for (i = 0; i < EX_HASH_ARR_SIZE; ++i)
    {
        if (i % 16 == 0)
        {
            fprintf(stdout, "\n%s%s", g_ts, g_ts);
        }
        else
        {
            fprintf(stdout, " ");
        }

        print_arr_elem(e, i);
    }
    fprintf(stdout, "\n%s},\n\n", g_ts);

    // print `next'
    if (e->next == NULL)
    {
        fprintf(stdout, "%sNULL", g_ts);
    }
    else
    {
        fprintf(stdout, "%s&%s%d", g_ts, var_prefix, level + 1);
    }

    fprintf(stdout, "\n};\n\n");
}

static int compare_sums(const void * lhs, const void * rhs)
{
    const ExHashInt l = *((ExHashInt *)lhs);
    const ExHashInt r = *((ExHashInt *)rhs);
    return l - r;
}

static void print_sums(struct ExHashCalcTables * ex_hash_calc, size_t strs_count, const char * var_prefix)
{
    fprintf(stdout, "static ExHashInt %ssums[] =\n{", var_prefix);

    qsort(ex_hash_calc->sums, strs_count, sizeof(ExHashInt), &compare_sums);

    size_t i;
    for (i = 0; i < strs_count; ++i)
    {
        fprintf(stdout, "\n%s0x%08X,", g_ts, ex_hash_calc->sums[i]);
    }

    fprintf(stdout, "\n};\n\n");
}


static void print_search_func(const char * var_prefix, const char * func_name, bool empty)
{
    fprintf(stdout, "// search function entry point\n");
    fprintf(stdout, "bool %s(const char * str, size_t str_len)\n{\n", func_name);

    if (empty)
    {
        fprintf(stdout, "%sreturn false;", g_ts);
    }
    else
    {
        fprintf(stdout,
                "%sExHashInt hash = ex_hash_table_get_sum(str, str_len, &%s0);\n"
                "%sif (hash == EX_HASH_INT_MAX)\n%s{\n%s%sreturn false;\n%s}\n\n"
                "%sreturn ex_hash_sums_bsearch_contains(hash, %ssums, sizeof(%ssums) / sizeof(%ssums[0]));",
                g_ts, var_prefix, // hash = ...
                g_ts, g_ts, g_ts, g_ts, g_ts, // if (hash...) ...
                g_ts, var_prefix, var_prefix, var_prefix // return
                );
    }

    fprintf(stdout, "\n};\n\n");
}

static void print_ex_hash_table(struct ExHashCalcTables * ex_hash_calc, size_t strs_count, const char * func_name, const char * var_prefix)
{
    bool empty;

    if (ex_hash_calc->etables != NULL)
    {
        rec_print(ex_hash_calc->etables, var_prefix, 0);
        print_sums(ex_hash_calc, strs_count, var_prefix);
        empty = false;
    }
    else
    {
        fprintf(stdout, "// generated table is nil\n");
        empty = true;
    }

    print_search_func(var_prefix, func_name, empty);
}


#define STRS_BUF_GROW_SIZE  (16)

static int prog_main(int argc, const char ** argv)
{
    int result = 0;
    FILE * file = NULL;
    char * str_buf = NULL;
    size_t str_buf_size;
    const char ** strs = NULL;
    int strs_count = 0;
    int strs_allocated = 0;
    struct ExHashCalcTables * ex_hash_calc = NULL;
    const char * file_name;
    const char * var_prefix;
    const char * func_name;

    if (argc < 4)
    {
        fprintf(stderr, "usage: exhashgen {file-name} {var-prefix} {search-func-name}\n");
        result = -1;
    }

    // get input parameters; try open file
    if (!result)
    {
        // open file
        file_name = argv[1];
        file = fopen(file_name, "rb");
        if (!file)
        {
            fprintf(stderr, "error: can't open %s\n", file_name);
            result = -2;
        }

        // get variable prefix
        var_prefix = argv[2];

        // get search function name
        func_name = argv[3];
    }

    // try read strings array
    if (!result)
    {
        // get file size
        fseek(file, 0, SEEK_END);
        size_t file_size = (size_t)ftell(file);

        // rewind back
        fseek(file, 0, SEEK_SET);

        // allocate strings buffer
        str_buf_size = file_size + 1;
        str_buf = xmalloc(str_buf_size);
        size_t bytes_read = fread(str_buf, 1, file_size, file);

        // put trailing zero
        str_buf[bytes_read] = 0;

        if (bytes_read != file_size)
        {
            fprintf(stderr, "error: can't read all the data\n");
            result = -3;
        }
    }

    // map strings buffer to strs (e.g. split to strings)
    if (!result)
    {
        char * begin = str_buf;
        char * end = str_buf + str_buf_size;
        char * cur = begin;

        for ( ; begin != end; ++begin)
        {
            if ((unsigned)*begin <= ' ')
            {
                // get rid of spaces
                *begin = 0;

                // realloc strs buffer if needed
                if (strs_count == strs_allocated)
                {
                    strs_allocated += STRS_BUF_GROW_SIZE;
                    strs = xrealloc(strs, strs_allocated * sizeof(char *));
                }

                // get rid of leading spaces
                while (*cur <= ' ' && cur < begin)
                {
                    ++cur;
                }

                // add non-empty string
                if (cur != begin)
                {
                    strs[strs_count++] = cur;
                }

                cur = begin;
            }
        }
    }

    // print file header
    // strs + strs_count are ready, print 'em as comments
    if (!result)
    {
        int si;

        fprintf(stdout,
                "// this is an auto-generated table for exact-hash calculation\n"
                "// generated from '%s' using variable prefix '%s'\n"
                "// for using in your code refer to 'struct ExHashTable %s0' variable in your code\n"
                "//\n"
                "// source strings are:\n",
                file_name, var_prefix, var_prefix);
        for (si = 0; si < strs_count; ++si)
        {
            fprintf(stdout, "// %02d: '%s'\n", (int)si, strs[si]);
        }
        fprintf(stdout, "// total: %d string(s)\n", strs_count);

        // include exhash.h
        fprintf(stdout,
                "\n#include \"exhash/exhash.h\""
                "\n\n");
    }

    // calculate exact-hash entries
    if (!result)
    {
        ex_hash_calc = create_ex_hash_tables(strs, strs_count);
        if (ex_hash_calc == NULL)
        {
            fprintf(stderr, "error: a given string list is too big, can't create ex-hash table\n");
            result = -4;
        }
    }

    if (!result)
    {
        print_ex_hash_table(ex_hash_calc, strs_count, func_name, var_prefix);
    }

    // free resources
    if (!file)
    {
        fclose(file);
    }

    xfree(str_buf);

    free_ex_hash_calc_tables(ex_hash_calc);

    return result;
}

int main(int argc, const char ** argv)
{
    return prog_main(argc, argv);
#if 0
    const char * p_argv[] =
    {
        "dummy", "D:/proj/tmp/geo/gen/admin-units-codes-list"
    };
    int p_argc = 2;
    return prog_main(p_argc, p_argv);
#endif
}
